package org.example.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpHeaders;
import org.example.gateway.config.AuthParamsConfig;
import org.example.gateway.config.Constant;
import org.example.gateway.utils.MatchUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * todo
 * todo 记录日志请求
 * todo 解析 token
 * todo 重写request 请求，将用户信息封装到 请求头中
 * todo 超级管理员 admin
 */
@Slf4j
@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Autowired
    private AuthParamsConfig authParamsConfig;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // login 路由直接转发
        String uri = exchange.getRequest().getURI().getPath();
        boolean isPass = authParamsConfig.getPassableUrls().stream().allMatch(uri::matches);
        if (isPass) {
            return chain.filter(exchange);
        }
        List<String> authorizationHeaders = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
        if (CollectionUtils.isEmpty(authorizationHeaders) || ObjectUtils.isEmpty(authorizationHeaders.get(0))) {
            return verificationFailed(exchange, "鉴权失败，请重新登录！");
        }
        try {
            String result = checkToken(uri, authorizationHeaders.get(0));
            if (StringUtils.hasLength(result)) {
                return verificationFailed(exchange, result);
            }
            return chain.filter(exchange);
        } catch (JwtException e) {
            log.error("JWT 解析异常", e);
            return verificationFailed(exchange, "鉴权失败，请重新登录！");
        } catch (Exception e) {
            log.error("权限校验异常", e);
            return verificationFailed(exchange, "服务内部错误！");
        }
    }

    /**
     * 解析 token ，匹配路由
     */
    private String checkToken(String uri, String token) {
        Claims claims = parseJWT(token);
        Date expiration = claims.getExpiration();
        if (claims.getSubject() == null || (expiration != null && expiration.before(new Date()))) {
            return "登录超时，请重新登录！";
        }
        String userId = (String) claims.get(Constant.USER_ID);
        String roleId = (String) claims.get(Constant.ROLE_ID);
        Boolean roleFlag = redisTemplate.opsForSet().isMember(Constant.REDIS_USER_ROLE_PREFIX + userId, roleId);
        if (!Boolean.TRUE.equals(roleFlag)) {
            return "鉴权失败，请重新登录！";
        }
        Set<Object> routes = redisTemplate.opsForSet().members(Constant.REDIS_ROLE_ROUTE_PREFIX + roleId);
        if (!CollectionUtils.isEmpty(routes)) {
            for (Object route : routes) {
                if (MatchUtil.isMatchByWildcard(uri, String.valueOf(route))) {
                    return null;
                }
            }
        }
        return "鉴权失败，请重新登录！";
    }

    private Mono<Void> verificationFailed(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("serverCode", 401);
        jsonObject.put("data", message);
        jsonObject.put("message", message);
        byte[] bits = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


    /**
     * 解密jwt
     */
    private Claims parseJWT(String token) {
        // 签名秘钥，和生成的签名的秘钥一模一样
        return Jwts.parser().setSigningKey(authParamsConfig.getJwtSecret()).parseClaimsJws(token).getBody();
    }

    /**
     * 由字符串生成加密key
     */
    private SecretKey generalKey() {
        // 本地的密码解码
        byte[] encodedKey = Base64.decodeBase64(authParamsConfig.getJwtSecret());
        // 根据给定的字节数组使用AES加密算法构造一个密钥
        return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    }


    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}
